﻿#include "timer.h"
#include <map>
#include <Windows.h>
#include <boost/thread/mutex.hpp>
#include <tchar.h>
#include "singlton.h"

#define TIMERWND_CLASSNAME _T("@#$^TXTimer_Class@#$^")

struct TimerMgr : public Singleton<TimerMgr>
{
    struct Item
    {
        Item(Method<void()> callback, int time_ms, void* key, DWORD tick)
            : callback(callback), time_ms(time_ms), key(key), tick(tick) {}
        Method<void()> callback;
        int time_ms;
        void* key;
        DWORD tick;
    };

    typedef std::list<Item> ItemList;
    ItemList items;
    typedef boost::mutex MutexType;
    typedef boost::lock_guard<boost::mutex> LockGuard;
    MutexType mutex;
    volatile int version;
    volatile int version_follow;
    volatile DWORD first_tick;

    static inline bool GreaterThan(DWORD a, DWORD b)
    {
        return a > b || (a >> 30) + 1 < (b >> 30);
    }

    void OnTimer()
    {
        DWORD start = GetTickCount();
        if(version_follow == version && GreaterThan(first_tick, start) )
            return;
        Method<void()> m;
        while(!items.empty())
        {
            {
                LockGuard lock(mutex);
                Item& item = *(items.front());
                first_tick = item.tick;
                if(GreaterThan(first_tick, GetTickCount()))
                {
                    version_follow = version;
                    break;
                }
                
                m = item.callback;
                if(item.time_ms < 0)
                {
                    item.tick -= item.time_ms;
                    _Add(item);
                }
                items.pop_front();
                if(items.empty())
                    EnsureTimerStop();
                version_follow = version;
            }
            m();
            if(GreaterThan(start + 10, GetTickCount()))
                break;
        }
    }

    inline void _Add(const Item& item)
    {
        DWORD cur = GetTickCount();
        LockGuard lock(mutex);
        for(ItemList::iterator it = items.begin(); it != items.end(); ++it)
        {
            if(GreaterThan(it->tick, item.tick))
            {
                if(it == items.begin())
                    version++;
                items.insert(it, item);
                break;
            }
        }
    }

    void Add(int time_ms, Method<void()> callback, void* key)
    {
        Item item(time_ms, callback, key, GetTickCount() + abs(time_ms));
        {
            LockGuard lock(mutex);
            _Add(item);
            EnsureTimerStart();
        }
    }

    bool Remove(void* key)
    {
        LockGuard lock(mutex);
        for(ItemList::iterator it = items.begin(); it != items.end(); ++it)
            if(it->key == key)
            {
                items.erase(it);
                if(items.empty())
                    EnsureTimerStop();
                return true;
            }
        return false;
    }
    size_t RemoveAll(void* key)
    {
        size_t n = 0;
        LockGuard lock(mutex);
        for(ItemList::iterator it = items.begin(), it2; it != items.end(); )
        {
            it2 = it;
            ++it;
            if(it2->key == key)
            {
                items.erase(it2);
                n++;
            }
        }
        if(items.empty())
            EnsureTimerStop();
        return n;
    }

    HWND m_hWnd;
    DWORD m_dwMainThreadId;
    bool has_timer;
    TimerMgr()
        : has_timer(false)
        , version(1)
        , version_follow(0)
    {
        WNDCLASSEX wc    = {sizeof(wc)};
        wc.hInstance     = GetModuleHandle(0);
        wc.lpszClassName = TIMERWND_CLASSNAME;
        wc.lpfnWndProc   = &TimerWndProc;
        wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
        wc.cbWndExtra    = sizeof(void*);
        RegisterClassEx(&wc);
        m_hWnd = CreateWindowEx(0, wc.lpszClassName, _T("Timer Helper Window"), WS_OVERLAPPED, 0, 0, 100, 100, 
            HWND_MESSAGE, NULL, wc.hInstance, this);
        ShowWindow(m_hWnd, SW_HIDE);
        m_dwMainThreadId = GetCurrentThreadId();
    }
    ~TimerMgr()
    {
        DestroyWindow(m_hWnd);
    }

    void EnsureTimerStart()
    {
        if(!has_timer)
        {
            SetTimer(m_hWnd, 1024, 10, NULL);
            has_timer = true;
        }
    }
    void EnsureTimerStop()
    {
        if(has_timer)
        {
            KillTimer(m_hWnd, 1024);
            has_timer = false;
        }
    }
    static LRESULT CALLBACK TimerWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
    {
        if(uMsg == WM_TIMER)
        {
            GetInstance().OnTimer();
            return 1;
        }
        return DefWindowProc(hWnd, uMsg, wParam, lParam);
    }
};

void SetTimeCallback(int time_ms, Method<void()> callback, void* key)
{
    TimerMgr::GetInstance().Add(time_ms, callback, key);
}

bool EraseTimeCallback(void* key)
{
    if(key == 0)
        return false;
    return TimerMgr::GetInstance().Remove(key);
    //return TimerMgr::GetInstance().RemoveAll(key);
}
